feat(threads): thread drawer, thread browser, live reply counts, and unread badges#564
Open
Just-Insane wants to merge 5 commits intoSableClient:devfrom
Open
feat(threads): thread drawer, thread browser, live reply counts, and unread badges#564Just-Insane wants to merge 5 commits intoSableClient:devfrom
Just-Insane wants to merge 5 commits intoSableClient:devfrom
Conversation
29282cb to
740e69c
Compare
Contributor
There was a problem hiding this comment.
Pull request overview
Adds end-to-end Matrix thread support across the room timeline: a thread drawer for reading/replying in-thread, a thread browser listing room threads (with pagination), and live reply/unread indicators, backed by Matrix SDK threadSupport.
Changes:
- Enable SDK thread tracking by passing
threadSupport: truetostartClient(classic + sliding sync). - Introduce shared edit-mode hook (
useMessageEdit) and wire it throughRoomTimeline/timeline actions andThreadDrawer. - Implement thread UI primitives:
ThreadDrawerported to the shared timeline rendering pipeline,ThreadBrowserbacked by/threads+ pagination/backfill, and live reply/unread badges on timeline thread chips.
Reviewed changes
Copilot reviewed 10 out of 10 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| src/client/initMatrix.ts | Enables Matrix SDK thread support in both classic and sliding sync start paths. |
| src/app/hooks/useMessageEdit.ts | New hook to centralize edit-mode state/reset/focus behavior for composers. |
| src/app/hooks/timeline/useTimelineEventRenderer.tsx | Adds live thread reply/unread chip updates; supports suppressing thread chip/reply headers inside drawer. |
| src/app/hooks/timeline/useTimelineActions.ts | Refactors timeline actions to accept shared handleEdit instead of owning edit state. |
| src/app/hooks/timeline/useProcessedTimeline.ts | Adds skipThreadFilter to allow processing thread timelines without filtering out replies. |
| src/app/features/room/ThreadDrawer.tsx | Reworks thread drawer to use shared timeline processing/rendering; adds sequential loading/backfill and edit-last-message support. |
| src/app/features/room/ThreadBrowser.tsx | Adds server-backed thread list with load-more pagination, root-event backfill, and unread badges. |
| src/app/features/room/RoomTimeline.tsx | Adopts useMessageEdit and passes handleEdit into timeline actions. |
| src/app/features/common-settings/developer-tools/DevelopTools.tsx | Adds thread diagnostics + “fetch from server” action for debugging thread state. |
| .changeset/feat-thread-enhancements.md | Declares a minor release note for thread support features. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Just-Insane
added a commit
to Just-Insane/Sable
that referenced
this pull request
Mar 28, 2026
- useTimelineEventRenderer.tsx: Use Math.max(thread.length, replyEvents.length) to avoid undercounting replies when only a subset of events is loaded locally. - ThreadBrowser.tsx: Use Math.max(thread.length, localReplyCount) to show correct total even for threads whose timeline hasn't been fully paginated. - ThreadDrawer.tsx: Align showClientUrlPreview logic with RoomTimeline: require both clientUrlPreview AND encClientUrlPreview in encrypted rooms. - useMessageEdit.ts: Minor wording: 'never stales' → 'never goes stale'.
Just-Insane
added a commit
to Just-Insane/Sable
that referenced
this pull request
Mar 28, 2026
- useTimelineEventRenderer.tsx: Use Math.max(thread.length, replyEvents.length) to avoid undercounting replies when only a subset of events is loaded locally. - ThreadBrowser.tsx: Use Math.max(thread.length, localReplyCount) to show correct total even for threads whose timeline hasn't been fully paginated. - ThreadDrawer.tsx: Align showClientUrlPreview logic with RoomTimeline: require both clientUrlPreview AND encClientUrlPreview in encrypted rooms. - useMessageEdit.ts: Minor wording: 'never stales' → 'never goes stale'.
a0e4ef7 to
b595e6b
Compare
…ges, and UI fixes
Fired on every room message, causing N×chip re-renders and O(n_events) scans per message when N threads are visible. ThreadEvent.NewReply/Update and RoomEvent.Redaction already cover the relevant updates.
d3520ef to
6709a58
Compare
Member
7w1
requested changes
Mar 31, 2026
| !!threadObjForLoading && !threadObjForLoading.initialEventsFetched && replyEvents.length === 0; | ||
|
|
||
| replyEventsRef.current = replyEvents; | ||
| // Automatically paginate backwards until all older replies are loaded. |
Member
There was a problem hiding this comment.
We probably shouldn't be paginating backwards until all replies are loaded? If its a huge thread with like 2000 replies we should just paginate when the user scrolls up, not automatically, unless I'm misunderstanding something
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

📝 Docs: Already live at Threads — Sable Docs — no separate docs PR needed.
Description
Expands Matrix thread support with a full thread drawer, server-side thread browser, live reply counts, unread badges, and several UX fixes.
ThreadDrawer
m.relates_to.event_idnotthreadRootIddirectly — fetches target event to check thread membership)maxHeight: 200pxto avoid dead whitespace on short messagesThreadBrowser
room.fetchRoomThreads()on mount to load threads beyond the local timeline windowuseRoomNavigate) instead of re-opening the drawerroom.getThreadUnreadNotificationCount()ThreadReplyChip (main timeline)
room.getThreadUnreadNotificationCount()Thread button (RoomViewHeader)
aria-pressedreflects both thread browser state and open thread drawerFixes #
Type of change
Checklist:
AI disclosure:
ThreadDrawer/ThreadBrowser/thread-chip iterations were partially AI assisted; I reviewed and adjusted logic for timeline processing, thread loading, and event rendering behavior.